/*
 * Copyright 2006 - 2007 The MITRE Corporation. All rights reserved.
 * Written by Ayal Spitz
 *
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1
 * (the "License"); you may not use this file except in compliance
 * with the License and the following two conditions:
 * 
 * The US Government will not be charged any license fee and/or royalties 
 * related to this software.
 * 
 * Neither name of The MITRE Corporation; nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * You may obtain a copy of the License at
 *
 *     http://www.gnu.org/copyleft/lesser.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

package org.mitre.streamingRSS.xmpp;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smackx.muc.MultiUserChat;

/**
 * This SubThread class is designed to provide a new thread per chat room so
 * that listeners to that chat room can survive.
 * 
 * @author Ayal Spitz
 *
 */
public class SubThread implements Runnable{
//### Class Fields ############################################################
	private Subscriber			parent = null;
	private String				mucName = null;
	private MultiUserChat		muc = null;
	
	private Set<SubListener>	listenerSet = null;
	private boolean				done = false;
	private Thread				thread = null;

// ### Class Constructor ######################################################
	/**
	 * This constructor creats a new subscription listener thread given the
	 * parent subscriber and the name of the chat room to listen to.
	 * 
	 * @param	parent		The Subscriber object that created this thread
	 * @param	mucName		The name of the chat room to listen to
	 */
	public SubThread(Subscriber parent, String mucName){
		this.parent = parent;
		this.mucName = mucName;
		
		listenerSet = new HashSet<SubListener>();
	}
	
// ### Class Methods ##########################################################
	/**
	 * This method adds a subscription listener and if the thread isn't running
	 * starts it up.
	 * 
	 * @param subList	The subscription listener
	 */
	public void add(SubListener subListener){
		listenerSet.add(subListener);
		subListener.setMUC(muc);
		if (thread == null){
			thread = new Thread(this);
		}
		start();
	}

	public void remove(SubListener subListener){
		listenerSet.remove(subListener);
		if (listenerSet.isEmpty()){ stop(); }
	}
	
// ============================================================================
	/**
	 * This method implements the Runnable method of 'run'. It's what gets
	 * executed when the thread starts running. It will continualy attempt to
	 * read messages.
	 */
	public void run(){	
		try{
			
			Message					msg = null;
			List<String>			msgList = null;
	
			// Create a message que
			msgList = new ArrayList<String>();
			
			waitForMUC();
			fireMUCJoined();
			
			// Loop until the done flag is fliped
			do{
				// Clear the message que
				msgList.clear();
				// Wait 5 seconds for a message
				msg = muc.nextMessage(5000);
				// Check to see if we've read a message
				if (msg != null){
					// Add the message to the message que
					msgList.add(msg.getBody());
					// See if we have more messages waiting
					do{
						// Wait 1 second for another message
						msg = muc.nextMessage(1000);
						if (msg != null){ msgList.add(msg.getBody()); }
					// Keep going if we've read a message
					}while(msg != null);
				}
				// Let all the listeners know we have a stack of messages waiting for
				// them
				if (!msgList.isEmpty()){ fireNewMessage(msgList); }
				if (!mucGood()){ waitForMUC(); }
			}while(!done);
		}catch(XMPPException e){
			e.printStackTrace();
		}
	}
	
	/**
	 * This method starts the thread and flips the done flag to false
	 */
	public void start(){
		done = false;
		thread.interrupt();
		thread.start();
	}
	
	/**
	 * This method stops the thread by flipping the done flag to false
	 */
	public void stop(){ done = true; }

	
	/**
	 * This method waits for the muc to come into existance and be open
	 * for this user to connect to it. Throws an XMPPException if the
	 * exception recived has anything but and XMPP error code of 404 (Not found)
	 * 
	 * @throws XMPPException
	 */
	private void waitForMUC() throws XMPPException{
		boolean					mucGood = false;
		XMPPError				xmppError = null;
		int						errorCode = -1;
		
		// Test to see if the muc exists and can be accessed
		do{
			try{
				muc = parent.joinChatRoom(mucName);
				mucGood = true;
			}catch(XMPPException e){
				xmppError = e.getXMPPError();
				if (xmppError != null){
					errorCode = xmppError.getCode();
					// If the errorCode is anything but 404 (Not found) that
					// means something has gone wrong and the user should be
					// notified
					if (errorCode != 404){ throw e; }
				}
			}
			// If the muc isn't good yet then wait 5 seconds
			if (!mucGood){
				try{
					Thread.currentThread().sleep(5000);
				}catch(InterruptedException e){}
			}
		}while(!mucGood && !done);
	}
	
	private boolean mucGood() throws XMPPException{
		boolean			flag = false;
		
		try{
			MultiUserChat.getRoomInfo(parent.xmppConnection, mucName);
			flag = true;
		}catch(XMPPException e){
			XMPPError				xmppError = null;
			int						errorCode = -1;
			
			xmppError = e.getXMPPError();
			if (xmppError != null){
				errorCode = xmppError.getCode();
				// If the errorCode is anything but 404 (Not found) that
				// means something has gone wrong and the user should be
				// notified
				if (errorCode != 404){ throw e; }
			}
			
		}
		
		return flag;
	}
// ============================================================================
	/**
	 * This method notifies all the listeners that we have a stack of messages
	 * for them to read.
	 * 
	 * @param msgList		The stack of messages for the listeners to be
	 * 						notified of
	 */
	protected void fireNewMessage(List<String> msgList){
		for(SubListener listener:listenerSet){
			listener.newMsgs(msgList);
		}
	}

	/**
	 * This method notifies all the listeners that we have successfuly joined
	 * a MUC
	 *
	 */
	protected void fireMUCJoined(){
		for(SubListener listener:listenerSet){
			listener.setMUC(muc);
		}
	}
}
